#!/bin/bash
#=======================================================================================
#  Name:  autofan
#=======================================================================================
#  Description:
#
#  A simple script to check for the highest hard disk temperature and then set the
#  fan to an apropriate speed. Fan needs to be connected to a motherboard with pwm
#  support.
#
#  How to invoke in your "go" script (copy to /boot/scripts):
#  chmod +x /boot/scripts/fan_speed.sh -- superseeded by Dynamix plugin
#  /boot/fan_speed.sh                  -- superseeded by Dynamix plugin
#=======================================================================================
# Version 1.0   Authored by Aiden
# Version 1.1   Modified by Dan Stroot to run in a loop. Does not require the user
#               to add this to cron     - just start it in your go file.
# Version 1.2   Modified by Guzzi       - removed -d ata to work on sas controllers
# Version 1.3   Modified by gfjardim    - added to dynamix.system.autofan
# Version 1.4   Modified by Bergware    - added additional line parameters
#                                       - start/stop control by Dynamix plugin
#                                       - celsius/fahrenheit conversion
# Version 1.5   Modified by Bergware    - added nvme devices temperature reading (contributed by syntactic-salt)
#                                       - added exclude disks from temperature reading (contributed by andreiio)
# Version 1.6   Modified by InfinityMod - added mulifan support
#                                       - added fan spin up/down offset 
#=======================================================================================
# Dependencies: grep,awk,smartctl,hdparm
#=======================================================================================
version=1.6
program_name="autofan"
usage() {
 echo "Usage: $program_name [-t min_temp] [-T max_temp] [-m loop in minutes] [-c controller] [-f fan] [-l low]"
 echo "       $program_name -V      = print program version "
 echo "       $program_name -q      = quit the program if it is running"
 echo
 echo " Argument descriptions:"
 echo " -t NN    =   set the low disk temp, below this temp the fan is off (default=35C)"
 echo " -T NN    =   set the high disk temp, above this temp the fan is 100% (default=45C)"
 echo " -c name  =   specify name of the controller (default=/sys/class/hwmon/hwmon2/pwm1)"
 echo " -f name  =   specify name of the fan (default=/sys/class/hwmon/hwmon2/fan1_input)"
 echo " -l NN    =   set value to slow down the fan speed (default=35)"
 echo " -m NN    =   number of minutes to wait between fan speed changes (default=5)"
 echo " -e name  =   drives to exclude from max temp calculation (optional)"
}
#=======================================================================================
# USER DEFINED VARIABLES: *MUST* BE SET TO *YOUR* VALUES
#=======================================================================================
# Fan device. Depends on *your* system. pwmconfig can help with finding this out.
# pwm1 is usually the cpu fan. You can "cat /sys/class/hwmon/hwmon1/device/fan4_input"
# to see the current rpm of the fan. If 0 then fan is off or there is no fan connected
# or motherboard can't read rpm of fan.
PWM_CONTROLLER=/sys/class/hwmon/hwmon2/pwm1 # Power (speed) setting
PWM_FAN=/sys/class/hwmon/hwmon2/fan1_input  # Used to track actual rpm values

# Temperature boundaries
TEMP_LOW=35        # Anything this number and below - fan is *off*
TEMP_HIGH=45       # Anything this number and above - fan is *full*

# Fan speed settings. Run pwmconfig (part of the lm_sensors package) to determine
# what numbers you want to use for your fan pwm settings. Should not need to
# change the OFF variable, only the LOW and maybe also HIGH to what you desire.
# Any real number between 0 and 255.
PWM_OFF_OFFSET=30  # Offset on turning off PWM = (PWM_LOW - PWM_OFF_OFFSET)
PWM_ON_OFFSET=30   # Offset on turning on PWM = (PWM_CALCULATED + PWM_ON_OFFSET)
PWM_OFF=0          # Off Value (note: many PWM fans will not turn off)
PWM_LOW=35         # Value to make your fan go slow
PWM_HIGH=255       # Value to make your fan go fast

INTERVAL=5         # The default number of minutes to loop
EXCLUDE=""         # By default no disks are excluded
#=======================================================================================
# PROGRAM LOGIC - CHANGE AT YOUR PERIL ;)
#=======================================================================================
# Get User Input
while getopts "t:T:m:c:f:l:qhVe:" opt; do
  case $opt in
    t) TEMP_LOW=$OPTARG ;;
    T) TEMP_HIGH=$OPTARG ;;
    m) INTERVAL=$OPTARG ;;
    c) PWM_CONTROLLER=$OPTARG ;;
    f) PWM_FAN=$OPTARG ;;
    l) PWM_LOW=$OPTARG ;;
    V) echo $program_name version: $version ; exit 0 ;;
    e) EXCLUDE=$OPTARG ;;
    h) usage >&2 ; exit 0 ;;
    q) quit_flag="yes" ;;
   \?) usage >&2 ; exit 0 ;;
  esac
done

#PID_NAME
PID_NAME="autofan_${PWM_CONTROLLER##*/}_${PWM_FAN##*/}"

show_values() {
  echo TEMP_LOW=$TEMP_LOW
  echo TEMP_HIGH=$TEMP_HIGH
  echo PWM_OFF=$PWM_OFF
  echo PWM_LOW=$PWM_LOW
  echo PWM_HIGH=$PWM_HIGH
  echo INTERVAL=$INTERVAL
}
#show_values                                            # uncomment for debugging

# validate the fan off temp
cc="$(echo $TEMP_LOW | sed 's/[0-9]//g')"
if [[ ! -z $cc ]]; then
  echo "Error: min fan temp must be numeric (whole number, not negative)." >&2
  usage >&2
  exit 2
fi

# validate the fan high temp
cc="$(echo $TEMP_HIGH | sed 's/[0-9]//g')"
if [[ ! -z $cc ]]; then
  echo "Error: max fan temp must be numeric (whole number, not negative)." >&2
  usage >&2
  exit 2
fi

# validate the minutes
cc="$(echo $INTERVAL | sed 's/[0-9]//g')"
if [[ ! -z $cc ]]; then
  echo "Error: minutes must be numeric (whole number, not negative)." >&2
  usage >&2
  exit 2
fi

# Lockfile processing
lockfile="/var/run/${PID_NAME}.pid"
if [[ -f $lockfile ]]; then
  # The file exists so read the PID
  # to see if it is still running
  lock_pid=`head -n 1 "${lockfile}"`
  pid_running=`ps -p "${lock_pid}" | grep ${lock_pid}`

  if [[ -z $pid_running ]]; then
    if [[ $quit_flag == no ]]; then
      # The process is not running
      # Echo current PID into lock file
      echo $$ > "${lockfile}"
    else
      echo "$program_name ${lock_pid} is not currently running "
      rm "${lockfile}"
    fi
  else
    if [[ $quit_flag == yes ]]; then
      echo killing $program_name process "$lock_pid"
      echo killing $program_name process "$lock_pid" | logger -t$program_name
      kill "$lock_pid"
      rm "${lockfile}"
      exit 0
    else
      echo "$program_name is already running [${lock_pid}]"
      exit 2
    fi
  fi
else
  if [[ $quit_flag == yes ]]; then
    echo "$program_name not currently running "
    exit 0
  else
    echo $$ > "${lockfile}"
  fi
fi

# Obtain the ID of your flash drive (your flash drive is named "UNRAID" right?)
flash=/dev/$(ls -l /dev/disk/by-label| grep UNRAID | cut -d/ -f3 | cut -c 1-3)

# Get list of included drives
if [[ -z $EXCLUDE ]]; then
  DRIVES=$(ls /dev/{[hs]d*[a-z],nvme[0-9]} | grep -v "$flash")
else
  DRIVES=$(ls /dev/{[hs]d*[a-z],nvme[0-9]} | grep -v "$flash" | grep -v "/dev/$(echo $EXCLUDE | sed -e $'s/,/\\\n/g' | sed -e 's/[np][0-9]//g')")
fi

# Count the number of drives in your array (ignoring the flash drive we identified)
NUM_OF_DRIVES=$(ls /dev/{[hs]d*[a-z],nvme[0-9]} | grep -v "$flash" | wc -l)

# Identify the included drives in your array so we can test their temperature
COUNT=1
for d in $DRIVES; do
  HD[$COUNT]=$d
  #echo HDD=${HD[$COUNT]}                               # Uncomment for debugging
  COUNT=$[$COUNT+1]
done

function_get_highest_hd_temp() {
  HIGHEST_TEMP=0
  for DISK in "${HD[@]}"; do
    SLEEPING=`hdparm -C ${DISK} | grep -c standby`
    if [[ $SLEEPING -eq 0 ]]; then
      if [[ $DISK == /dev/nvme[0-9] ]]; then
        CURRENT_TEMP=$(smartctl -A $DISK | awk '$1=="Temperature:" {print $2;exit}')
      else
        CURRENT_TEMP=$(smartctl -A $DISK | awk '$1==190||$1==194 {print $10;exit}')
      fi
      if [[ $HIGHEST_TEMP -le $CURRENT_TEMP ]]; then
        HIGHEST_TEMP=$CURRENT_TEMP
      fi
    fi
  done
}

function_get_current_fan_speed() {
  # Function to get current fan values
  CURRENT_FAN_SPEED=`cat $PWM_CONTROLLER`
  CURRENT_FAN_RPM=`cat $PWM_FAN`
  CURRENT_PERCENT_SPEED=$(($(($CURRENT_FAN_SPEED*100))/$PWM_HIGH))
  #echo Current Fan Speed = $CURRENT_FAN_SPEED          # Uncomment for debugging
  #echo Current Fan RPM = $CURRENT_FAN_RPM              # Uncomment for debugging
  #echo Current Percent Speed = $CURRENT_PERCENT_SPEED  # Uncomment for debugging
  if [[ $CURRENT_FAN_SPEED -le $PWM_OFF ]]; then
    CURRENT_OUTPUT="OFF (0% @ 0rpm)"
    #echo Current output = $CURRENT_OUTPUT              # Uncomment for debugging
  else
    if [[ $CURRENT_FAN_SPEED -ge $PWM_HIGH ]]; then
      CURRENT_OUTPUT="FULL (100% @ ${CURRENT_FAN_RPM}rpm)"
      #echo Current output = $CURRENT_OUTPUT            # Uncomment for debugging
    else
      CURRENT_OUTPUT="$CURRENT_FAN_SPEED (${CURRENT_PERCENT_SPEED}% @ ${CURRENT_FAN_RPM}rpm)"
      #echo Current output = $CURRENT_OUTPUT            # Uncomment for debugging
    fi
  fi
}

function_calc_new_fan_speed() {
  # Calculate new fan values based on highest drive temperature
  if [[ $HIGHEST_TEMP -le $TEMP_LOW ]]; then
    ADJUSTED_FAN_SPEED=$PWM_OFF
    ADJUSTED_PERCENT_SPEED=0
    ADJUSTED_OUTPUT="OFF"
  else
    if [[ $HIGHEST_TEMP -ge $TEMP_HIGH ]]; then
      ADJUSTED_FAN_SPEED=$PWM_HIGH
      ADJUSTED_PERCENT_SPEED=100
      ADJUSTED_OUTPUT="FULL"
    elif [ "$ADJUSTED_OUTPUT" = "OFF" ]; then
      ADJUSTED_FAN_SPEED=$(($(($(($HIGHEST_TEMP-$TEMP_LOW))*$FAN_PWM_INCREMENTS))+($PWM_LOW+$PWM_ON_OFFSET)))
      ADJUSTED_PERCENT_SPEED=$(($(($ADJUSTED_FAN_SPEED*100))/$PWM_HIGH))
      ADJUSTED_OUTPUT=$ADJUSTED_FAN_SPEED
    else
      ADJUSTED_FAN_SPEED=$(($(($(($HIGHEST_TEMP-$TEMP_LOW))*$FAN_PWM_INCREMENTS))+$PWM_LOW))
      ADJUSTED_PERCENT_SPEED=$(($(($ADJUSTED_FAN_SPEED*100))/$PWM_HIGH))
      ADJUSTED_OUTPUT=$ADJUSTED_FAN_SPEED
    fi
  fi
  #echo Adjusted output = $ADJUSTED_OUTPUT              # Uncomment for debugging
}

function_change_fan_speed() {
  # Implemenent fan speed change if neeeded
  if [[ $CURRENT_FAN_SPEED -ne $ADJUSTED_FAN_SPEED ]]; then
    # Enable speed change on fan
    if [[ `cat ${PWM_CONTROLLER}_enable` -ne 1 ]]; then
      echo 1 > "${PWM_CONTROLLER}_enable"
    fi
    # set fan to new value
    echo $ADJUSTED_FAN_SPEED > $PWM_CONTROLLER
    sleep 5
    # Get new rpm value
    ADJUSTED_FAN_RPM=`cat $PWM_FAN`
    ADJUSTED_OUTPUT="${ADJUSTED_OUTPUT} (${ADJUSTED_PERCENT_SPEED}% @ ${ADJUSTED_FAN_RPM}rpm)"
    # Output the change
    CFG=/boot/config/plugins/dynamix/dynamix.cfg
    [[ -f $CFG ]] && TEMP_UNIT=`grep -Po '^unit="\K[^"]+' $CFG` || TEMP_UNIT=C
    if [[ $TEMP_UNIT == F ]]; then
      REPORTED_TEMP=$((($HIGHEST_TEMP*9/5)+32))
    else
      REPORTED_TEMP=$HIGHEST_TEMP
    fi
    echo "$program_name: Highest disk temp is ${REPORTED_TEMP}${TEMP_UNIT}, adjusting fan speed from: $CURRENT_OUTPUT to: $ADJUSTED_OUTPUT"
    echo "Highest disk temp is ${REPORTED_TEMP}${TEMP_UNIT}, adjusting fan speed from: $CURRENT_OUTPUT to: $ADJUSTED_OUTPUT" | logger -t$program_name
  fi
}

# Main Loop
ADJUSTED_OUTPUT="OFF"
while [[ -f $lockfile ]]; do
  # Just wait if modules aren't loaded
  if [[ ! -f $PWM_FAN || ! -f $PWM_CONTROLLER ]]; then
    sleep $(($INTERVAL*60));
    continue;
  fi

  # Set PWM_OFF
  PWM_OFF=$(($PWM_LOW-$PWM_OFF_OFFSET))  # Off Value (note: many PWM fans will not turn off)

  if [[ $PWM_OFF -lt 0 ]]; then
    PWM_OFF=0
  fi

  # Disable fan mininum RPM
  FAN_RPM_MIN=$(echo ${PWM_FAN} | cut -d"_" -f1)"_min"
  if [[ $(cat $FAN_RPM_MIN) -ne 0 ]]; then
    echo 0 > $FAN_RPM_MIN
  fi

  # Calculate size of increments.
  FAN_TEMP_INCREMENTS=$(($TEMP_HIGH-$TEMP_LOW))
  FAN_PWM_INCREMENTS=$(($(($PWM_HIGH-$PWM_LOW))/$FAN_TEMP_INCREMENTS))

  # Get highest drive temperature
  function_get_highest_hd_temp

  # Get current fan speed
  function_get_current_fan_speed

  # Calculate new fan speed
  function_calc_new_fan_speed

  # Change fan speed if necessary
  function_change_fan_speed

  #echo Sleeping for $INTERVAL minutes                  # uncomment for debugging
  sleep $(($INTERVAL*60))
done &

# while loop was put into background, now disown it so it will continue to run when you log off.
# to get it to stop, type: rm /var/lock/fan_speed.LCK
background_pid=$!
echo $background_pid > "${lockfile}"
echo "$program_name process ID $background_pid started, To terminate it, type: $program_name -q -c $PWM_CONTROLLER -f $PWM_FAN" >&2
echo "$program_name process ID $background_pid started, To terminate it, type: $program_name -q -c $PWM_CONTROLLER -f $PWM_FAN" | logger -t$program_name
if [[ -n $EXCLUDE ]]; then
echo "$program_name excluding drives $EXCLUDE from max temp calculations" | logger -t$program_name
fi
disown %%
